ReAct 范式
ReAct = Reasoning + Acting,出自 2022 年 10 月的论文《ReAct: Synergizing Reasoning and Acting in Language Models》(Shuny…
ReAct 范式:让模型边想边做,以及它没那么神的地方
阅读提示 本笔记面向已经理解 LLM、Prompt 和工具调用基本概念、正在学 Agent 的读者。主线是:ReAct 想解决什么 → 循环怎么转 → 怎么落地写对 → 它真实的效果边界 → 后人如何改进它 → 它和今天的 Function Calling 是什么关系。
前置概念见 Agent概念;框架层的工程分层见 Agent架构设计-lang系框架。
⚠️ = 常见陷阱 🆚 = 对比辨析 💡 = 机制或选择建议
目录
- 一、ReAct 到底解决了什么问题?
- 二、Thought-Action-Observation 循环怎么转?
- 三、ReAct 的 Prompt 长什么样?
- 四、落地时最容易写错的地方:停止符
- 五、ReAct 真的比 CoT 强吗?
- 六、ReAct 的失败模式与代价
- 七、后来者如何改进 ReAct?
- 八、ReAct 和 Function Calling 是什么关系?
- 常见说法订正
- 速查表
- 复习重点
核心概念 ReAct = Reasoning + Acting,出自 2022 年 10 月的论文《ReAct: Synergizing Reasoning and Acting in Language Models》(Shunyu Yao 等,普林斯顿大学 + Google Research,ICLR 2023)。
它的核心主张只有一句:让语言模型交替生成「推理轨迹」和「行动指令」,两者互相喂养——推理为行动定方向,行动的真实结果为推理提供纠偏依据。
论文标题里的关键词是 Synergizing(协同),不是"加上"。价值不在于"既能想又能做",而在于两者交错时产生的互补。
先做个名字消歧 前端的 React.js 是 Facebook 的 Jordan Walke 在 2013 年 5 月开源的 UI 框架,解决的是 DOM 操作和组件化问题,与本文毫无关系,只是拼写撞车。本笔记全篇讲的是 Agent 范式 ReAct。
一、ReAct 到底解决了什么问题?
在 ReAct 之前,让 LLM 完成复杂任务有两条互相独立的路线,各自都是瘸腿的。
只推理(CoT):给模型思维链示例,让它一步步推导。逻辑链条清晰,但整个过程封闭在模型参数内部,所有事实都来自训练记忆。问它今天的股价,它只能编。
只行动(Act):让模型直接输出工具调用,拿真实数据。事实可靠了,但中间没有推理痕迹,模型不知道自己为什么调这个工具,搜不到结果时也不会换个思路重试。
ReAct 的洞察是:人做事本来就是边想边干的——想一下、试一下、看结果、再想下一步。
上图从上到下读:前两行各自缺一半,第三行把两者交错起来,并用虚线标出「观察结果回流到下一轮思考」这条纠错通路。图中最值得记住的是底部两个箭头方向:Thought → Action 让行动有目的,Observation → Thought 让思考有依据。少了任何一个方向,就退化成前两行之一。
术语澄清:CoT 的两种形态 CoT = Chain-of-Thought(思维链)。原始论文(Wei et al., 2022)用的是 few-shot 方式,即在 Prompt 里给出几个带推理过程的范例。
而广为流传的那句
Let's think step by step,出自另一篇论文 Zero-shot CoT(Kojima et al., 2022)——不给范例,只用一句咒语触发推理。两者常被混为一谈,面试时容易被追问。
二、Thought-Action-Observation 循环怎么转?
整个范式就是一个循环,直到模型决定结束:
Thought: 我现在知道什么?下一步该做什么?为什么?
Action: 调用哪个工具,参数是什么
Observation: 工具返回的真实结果(不是模型生成的)
→ 回到 Thought举个例子,问:「除了苹果,还有哪家公司市值超过 2 万亿美元?」
Thought 1: 用户想知道除苹果外市值超 2 万亿的公司。我需要先查当前市值排名。
Action 1: search[市值超过 2 万亿美元的公司]
Observation 1: 微软、英伟达、Alphabet 等公司市值均超过 2 万亿……
Thought 2: 摘要提到了三家,但没给具体数字,我需要验证其中一家的准确市值。
Action 2: search[微软 最新市值]
Observation 2: 微软市值约 3.1 万亿美元。
Thought 3: 已验证微软符合条件,信息足够回答了。
Action 3: finish[除苹果外,微软、英伟达等公司市值也超过 2 万亿美元……]⚠️
finish是一个 Action,不是独立字段 错误写法: 把最终答案写成与Action:平级的新标签,例如Finish: 最终答案。实际结果: 解析器需要同时兼容两种输出格式;模型在 few-shot 里看到两套格式,容易在中途就直接吐出
Finish:而跳过必要的工具调用。原因: 在论文的 Wikipedia 问答环境里,
finish[answer]与search[entity]、lookup[string]是同一类东西——都是动作,只不过finish这个动作的语义是"终止并提交答案"。它不是一个新的输出通道。(注意finish本身也是该环境的设定,不是 ReAct 的通用要求,见下一节。)正确做法: 统一只解析
Action:一行,再判断动作名是否为finish:name, arg = parse_action(text) # 只有一种格式需要解析 if name == "finish": return arg # 终止条件收敛在一处 observation = TOOLS[name](arg)
Thought 不必每一步都有 论文在知识问答类任务(HotpotQA、FEVER)中让 thought 和 action 严格交替;但在决策类任务(ALFWorld、WebShop)中,thought 是稀疏的——只在关键决策点插入,中间连续执行若干 action。
原因是家务、购物这类任务里大量动作是机械的("走到桌子旁""打开抽屉"),每步都强行推理只会浪费 token 并增加跑偏概率。这是个实用的调参维度:任务越机械,thought 越该稀疏。
三、ReAct 的 Prompt 长什么样?
论文在 HotpotQA / FEVER 这两个知识问答任务里搭了一个基于 Wikipedia API 的环境,动作空间就三个动作:
| 动作 | 含义 |
|---|---|
search[entity] |
搜索实体,返回对应词条前若干句 |
lookup[string] |
在当前词条内查找包含该字符串的下一句(模拟浏览器 Ctrl+F) |
finish[answer] |
终止并给出答案 |
⚠️ 把这三个动作当成 ReAct 的通用动作空间 错误认知: "ReAct 就是 search / lookup / finish 这三个动作",于是实现自己的 Agent 时也照着搬一个
finish[...]动作,或者觉得动作必须长成动作名[参数]这个样子。实际情况: 这三个动作属于那个 Wikipedia 环境,不属于 ReAct。同一篇论文里的另外两个任务用的是完全不同的动作集:ALFWorld(文字版家务)用的是
go to、take、open、clean这类具身动作;WebShop(网购)用的是search、click这类网页操作。它们都不用finish[answer]结束——任务在环境判定目标达成时自然终止。原因: ReAct 规定的是交互结构(Thought / Action / Observation 交错),动作空间是环境给的。混淆这两层,就会把一个具体实验的细节当成范式要求。
正确做法: 设计自己的 Agent 时,动作空间从你的业务环境推导——有哪些工具、每个工具收什么参数。终止方式也一样:可以是一个显式的 finish 动作,也可以是"模型不再请求工具调用"(这正是今天 Function Calling 的做法),还可以是环境侧的目标判定。
Prompt 本身是 few-shot 的,结构如下:
Solve a question answering task with interleaving Thought, Action, Observation steps.
Action can be three types:
(1) search[entity] (2) lookup[string] (3) finish[answer]
Question: 姚明和奥尼尔谁更高?高多少?
Thought 1: 我需要分别查姚明和奥尼尔的身高,再做差值比较。先查姚明。
Action 1: search[姚明]
Observation 1: 姚明,中国前职业篮球运动员,身高 2.26 米……
Thought 2: 姚明 2.26 米。现在查奥尼尔。
Action 2: search[沙奎尔·奥尼尔]
Observation 2: 沙奎尔·奥尼尔,身高 2.16 米……
Thought 3: 2.26 > 2.16,姚明更高,差值 0.10 米。信息已完整。
Action 3: finish[姚明更高,比奥尼尔高约 0.10 米]
Question: {新问题}💡 这里模型学到的不是知识,而是输出格式和思考节奏。这就是"范式"一词的含义:它规定的是交互结构,不是任务内容。
⚠️ 不要把 ReAct 的强度全部归给 Prompt 设计 常见说法: "ReAct 强不是模型强,是 Prompt 设计天才。"
实际情况: 论文的主要实验建立在 PaLM-540B 上,并明确指出 ReAct 的收益依赖模型规模——小模型套用同样的 Prompt 效果显著劣化,甚至不如直接微调。
原因: 交错生成推理与结构化动作,要求模型同时具备长程规划能力和严格的格式遵循能力,这两项都是随规模涌现的。
正确理解: Prompt 结构是必要条件,模型能力是前提条件。把它当成"小模型也能靠 Prompt 变强"的方法会踩坑。
四、落地时最容易写错的地方:停止符
这是自己动手实现 ReAct 时第一个会踩、且踩了很难察觉的坑。
⚠️ 不设 stop sequence,模型会自己编造 Observation 错误操作: 直接调
llm(prompt),让模型自由生成到结束。实际结果: 模型会一口气把
Thought 1 / Action 1 / Observation 1 / Thought 2 / Action 2 …全部生成出来,包括本该由真实工具返回的 Observation。日志看上去完美无缺,但所有"观察"都是模型幻觉出来的,一次工具都没真正调用。原因: few-shot 示例里 Observation 就紧跟在 Action 后面。对模型而言这只是一个待续写的文本模式,它没有任何理由在此停笔——是我们外部要拦截它。
正确做法: 把
\nObservation设为停止符,强制模型每轮只生成到 Action 为止:text = llm(prompt, stop=["\nObservation"]) # 关键:截断在这里
带上这个细节后,最小实现是这样的:
MAX_STEPS = 8
def react(question: str) -> str:
history = ""
for step in range(1, MAX_STEPS + 1):
prompt = f"{FEWSHOT_PROMPT}\nQuestion: {question}\n{history}Thought {step}:"
# 1. 生成本轮的 Thought + Action,停在 Observation 之前
text = llm(prompt, stop=["\nObservation"])
# 2. 从文本里解析出动作名和参数
name, arg = parse_action(text) # 如 ("search", "姚明")
if name == "finish":
return arg
# 3. 真实执行工具,结果由外部写回,绝不让模型自己生成
observation = TOOLS[name](arg)
history += f"Thought {step}:{text}\nObservation {step}: {observation}\n"
return "达到最大步数仍未得出结论"这段代码里有三个决策点值得留意:停止符位置决定观察是否真实、finish 的判断位置决定终止逻辑是否唯一、MAX_STEPS 决定失控时能否收场。
五、ReAct 真的比 CoT 强吗?
这是最容易被科普文章带偏的一节。直觉上"能查资料的当然比只会背书的强",但论文数据并非如此。
以 HotpotQA(多跳问答,PaLM-540B)为例,精确匹配得分大致是:
| 方法 | HotpotQA EM | 说明 |
|---|---|---|
| CoT | ≈29.4 | 只推理,不查资料 |
| CoT-SC | ≈33.4 | CoT + 自洽性投票 |
| Act | ≈25.7 | 只行动,无推理 |
| ReAct | ≈27.4 | 单独使用时低于 CoT |
| ReAct + CoT-SC 混合 | ≈35.1 | 论文中的最佳配置 |
而在 ALFWorld(文字版家务决策任务)上,差距则完全反过来:ReAct 成功率约 71%,Act 约 45%,差了 26 个百分点。
这两组数据合起来说明什么 ReAct 的优势不在"知识问答准确率",而在"需要与环境多步交互的任务"。
在 HotpotQA 上 ReAct 落后于 CoT,是因为它被检索结果牢牢约束住了——搜到的段落不相关时,它不像 CoT 那样敢于用内部知识推理,反而被带偏。
论文因此提出混合策略:先跑 ReAct,若在规定步数内没有 finish,就回退到 CoT-SC;或反过来,CoT-SC 内部投票分歧大时切到 ReAct 查证。内部知识与外部知识各有适用面,切换才是最优解。
⚠️ "所有事实都来自 Observation,所以不会幻觉"是过强的结论 常见说法: ReAct 能彻底抗幻觉。
实际结果: 论文的错误分析里,ReAct 有相当比例的失败来自推理错误和检索到无信息量的结果。观察是真的,但模型对观察的解读可能是错的。
原因: ReAct 只保证「观察这一环」接地(grounded),不保证「从观察到结论这一环」正确。而且检索工具本身也会返回错误或过时内容。
正确表述: ReAct 显著降低事实性幻觉,并让幻觉可追溯(能从轨迹里看出是哪一步开始跑偏的),但不消除幻觉。
六、ReAct 的失败模式与代价
| 问题 | 具体表现 | 根因 |
|---|---|---|
| 调用次数多 | N 步任务需要 N+1 次 LLM 调用,且每次重传全部历史 | 交错结构决定每个 action 后必须回到模型 |
| 延迟与成本高 | 上下文随步数线性膨胀,token 消耗接近平方级增长 | 历史全量拼接进 Prompt |
| 重复动作死循环 | 反复用同一关键词搜索、反复生成相同 thought | 模型无法感知"我刚才试过且失败了" |
| 缺乏全局规划 | 走一步看一步,长任务中途迷路 | 没有独立的规划层,每步只看局部 |
| 格式脆弱 | 自由文本输出的 Action: xxx[yyy] 解析失败 |
纯文本约定,无结构化保证 |
⚠️ 重复动作死循环必须由外部打断 错误操作: 只设最大步数,指望模型自己意识到在重复。
实际结果: 模型会把"搜索失败 → 换个说法再搜 → 又失败"重复到步数耗尽,白白烧掉全部预算。
原因: 上一轮的失败在历史里只是一段普通文本,模型没有被显式要求去比对"这个动作我是否已经执行过"。
正确做法: 在循环外维护已执行动作的集合,命中重复时主动把提示写进 Observation,让模型看到:
key = (name, arg) if key in seen: observation = f"你已经执行过 {name}[{arg}] 且结果无效,请换一个动作或参数。" else: seen.add(key) observation = TOOLS[name](arg)关键在于反馈要走 Observation 通道——那是模型唯一会认真读的外部输入。
七、后来者如何改进 ReAct?
上图按行对照四种调用模式,蓝框是 LLM 调用、橙框是工具执行。读图重点是数蓝框个数:ReAct 每做一步就得回一次模型,ReWOO 把它压到两次。
ReWOO(Reasoning WithOut Observation,2023) 针对的是调用次数。它拆成 Planner / Worker / Solver 三段:Planner 一次性输出完整计划,计划中用 #E1、#E2 这类变量占位来引用前序步骤的结果;Worker 按计划执行工具,中途不再回到 LLM;最后 Solver 汇总成答案。典型情况下 LLM 调用从 N+1 次降到 2 次。
⚠️ ReWOO 不等于"所有工具并行执行" 计划里有依赖关系的步骤(第 3 步要用第 1 步的结果)仍然必须按序执行。ReWOO 省掉的是工具之间那些回到 LLM 的往返,不是工具执行本身的顺序性。代价也很直接:计划一次成型,中途拿到意外结果时无法调整策略。
Reflexion(2023) 针对的是失败经验不留存。任务失败后,让模型生成一段自然语言反思("上次因为关键词过于宽泛而失败,这次应更具体"),写入情景记忆,下一轮重跑时带上。论文称之为言语强化学习(verbal reinforcement learning)——用文本反馈替代梯度更新。
🆚 Reflexion 治的不是单轮死循环 它作用在轮次之间:整个任务失败一次,反思一次,再重试整个任务。而第六节说的重复动作,发生在单轮内部,得靠循环保护解决。两者常被混淆。
Plan-and-Execute 针对的是缺乏全局规划。先由 Planner 模型产出高层步骤清单,每个子任务再交给 ReAct 子循环去执行,执行完可以重新规划(replan)剩余步骤。在这个架构里 ReAct 降级成了执行层,不再负责全局路线。
八、ReAct 和 Function Calling 是什么关系?
⚠️ "现在的模型联网搜索,背后跑的就是 ReAct"并不准确 常见说法: 今天用的 GPT、Claude 一联网,内部就是在跑 ReAct 的 few-shot Prompt。
实际情况: 现代模型的工具调用是训练进模型里的原生能力,通过专门的消息角色和结构化 JSON 表达,不依赖 ReAct 那套 few-shot 文本模板。
原因: ReAct 用自由文本表达动作(
Action: search[姚明]),解析全靠正则,模型稍微换个写法就崩。厂商把这层约定收进训练和 API 契约,改成结构化输出,可靠性完全不是一个量级。正确表述: Function Calling 继承了 ReAct 的思想内核(模型决策 → 外部执行 → 结果回流 → 继续决策),但替换了它的实现载体。说"血脉相承"没问题,说"背后跑的就是 ReAct"就错了。
🆚 两者的同维度对照:
| 维度 | ReAct(原始) | Function Calling |
|---|---|---|
| 动作表达 | 自由文本 Action: search[x] |
结构化 {"name":"search","arguments":{...}} |
| 能力来源 | few-shot Prompt 诱导 | 模型训练时习得 |
| 解析方式 | 正则/字符串切分,易失败 | JSON 解析,schema 约束 |
| 推理痕迹 | Thought 显式写在文本里 | 可有可无(部分模型放在 reasoning 字段) |
| 停止控制 | 靠 stop sequence 人工截断 | API 层面通过 finish_reason 天然分段 |
| 并行调用 | 不支持 | 多数厂商支持一次返回多个调用 |
| 相同点 | 决策与执行分离、结果回流后继续决策的闭环 | 同左 |
💡 实践建议:如果模型支持原生 Function Calling,不要再手写 ReAct 文本模板。只在这两种情况下退回原始 ReAct:使用不支持工具调用的开源模型;教学与调试。
⚠️ 「为了审计所以要保留完整 Thought」是个站不住的理由 这条曾经被列为退回 ReAct 的第三个理由,但它把 Thought 的证明力估高了。
Thought 是模型生成的文本,不保证忠实反映它内部真实的决策依据。 Turpin 等人的实验(arXiv 2305.04388)已经证明:模型的答案可以明显被注入的偏置带偏,而它写出来的推理链只字不提这个偏置,转而编造一套合情合理的说辞。详见 CoT 笔记。
所以拿 Thought 去做审计,审的是模型的自述,不是它的实际行为。一段流畅的 Thought 反而会让错误决策显得更可信。
审计真正该落盘的是:调用了哪个工具、传了什么参数、返回了什么、什么时间、由谁授权。这些是可核对的事实,而且 Function Calling 的结构化输出天然就带这些字段,比解析自由文本更可靠。Thought 留着当调试线索很有价值,但它不是证据。
顺带一提,现在多数厂商也把推理内容放在专门的 reasoning 字段里返回,「要看推理过程就得手写 ReAct」这个前提本身也不再成立了。
常见说法订正
整理一份高频误传对照,面试和写文档时容易踩:
| 常见说法 | 是否准确 | 订正 |
|---|---|---|
| ReAct 2022 年 10 月由普林斯顿 + Google 提出 | ✅ 准确 | arXiv 2210.03629,ICLR 2023 接收 |
输出格式是 Thought / Action / Observation / Finish |
❌ 不准确 | finish[answer] 是一个 Action,不是独立标签 |
| ReAct 通过 Observation 彻底消除幻觉 | ❌ 过强 | 只保证观察接地,推理仍可能出错;应说"显著降低且可追溯" |
| ReAct 全面优于 CoT | ❌ 不准确 | HotpotQA 上单用 ReAct 反而低于 CoT,混合策略才最优 |
| ReAct 强在 Prompt 设计,与模型无关 | ❌ 不准确 | 论文明确指出收益依赖模型规模(PaLM-540B) |
| 后续框架"全部照抄"ReAct | ⚠️ 夸大 | 是主流内核之一,但 AutoGPT 等还依赖任务队列与长期记忆,并非纯 ReAct |
| 今天模型联网搜索背后跑的就是 ReAct | ❌ 不准确 | 是原生 Function Calling,思想继承但载体不同 |
| ReAct 的动作空间是 search / lookup / finish | ❌ 错误 | 那是 HotpotQA/FEVER 的 Wikipedia 环境专有;ALFWorld、WebShop 用完全不同的动作集 |
| 必须暴露完整 Thought 才能审计 Agent | ❌ 错误 | Thought 不保证忠实。审计要落盘工具调用与返回,Thought 只是调试线索 |
| ReWOO 一次性并行执行所有工具 | ⚠️ 不严谨 | 有依赖的步骤仍按序执行,省掉的是回 LLM 的往返 |
| Reflexion 解决死循环 | ⚠️ 偏差 | 它解决跨轮次的经验积累;单轮死循环靠循环保护 |
Let's think step by step 出自 CoT 原论文 |
❌ 不准确 | 出自 Zero-shot CoT(Kojima et al., 2022) |
速查表
| 概念 | 要点 |
|---|---|
| ReAct 全称 | Reasoning + Acting,强调 Synergizing(协同) |
| 核心循环 | Thought → Action → Observation → Thought → … → 终止 |
| Wikipedia 实验的动作空间 | search[entity] / lookup[string] / finish[answer]——只属于 HotpotQA/FEVER 环境,ALFWorld、WebShop 各有各的动作集 |
| 动作空间由谁决定 | 环境,不是 ReAct。ReAct 只规定 Thought/Action/Observation 的交错结构 |
| 实现第一坑 | 必须设 stop=["\nObservation"],否则模型自编观察结果 |
| 实现第二坑 | 必须有 MAX_STEPS 和重复动作检测 |
| Thought 密度 | 问答任务严格交替;决策任务可稀疏插入 |
| 最强配置 | ReAct 与 CoT-SC 互为回退,而非单用 |
| 成本模型 | N 步 ≈ N+1 次 LLM 调用,上下文随步数膨胀 |
| 三个改进方向 | ReWOO(省调用)/ Reflexion(存经验)/ Plan-Execute(补规划) |
| 今天怎么用 | 有原生 Function Calling 就用它,思想仍是 ReAct 的闭环 |
复习重点
复习重点
- 一句话说清 ReAct:交替生成推理与行动,让
Thought → Action赋予行动目的,让Observation → Thought提供纠偏依据;两个方向缺一即退化。- 最容易写错的实现细节:不设停止符,模型会连 Observation 一起编出来,全流程零真实工具调用,且日志毫无异常。
- 最容易记错的格式:
finish是动作之一,不是与 Action 平级的字段——但它本身也只是 Wikipedia 环境的设定,不是 ReAct 要求每个实现都有 finish 动作。- 最容易过度泛化的地方:
search / lookup / finish是那个实验环境的动作空间,不是 ReAct 的。动作空间来自环境,ReAct 只规定交互结构。- 最容易被科普带偏的结论:ReAct 不是全面碾压 CoT。它在多步环境交互任务上优势巨大,在纯知识问答上单用反而可能不如 CoT,混合才最优。
- Thought 不是审计凭证:它是模型生成的文本,不保证忠实。要审计就落盘工具调用与返回值。
- 选型规则:模型支持原生 Function Calling → 直接用;模型不支持 → 手写 ReAct;调用成本敏感且流程稳定 → 考虑 ReWOO;任务长且易迷路 → Plan-and-Execute 把 ReAct 降为执行层。
动手练习
前面第四节的最小实现只有 MAX_STEPS 这一道防线,不足以应对第六节说的重复动作死循环。
练习:在 react() 循环中加入循环保护,需要自己决定三件事——用什么粒度判定"重复"(动作名?动作名加参数?还是参数的语义相似度)、重复多少次才触发、触发后是直接中止还是把提示写回 Observation 让模型自救。三种选择各有代价,没有唯一答案。
一句话总结 ReAct 的贡献不是"让模型会用工具",而是把推理和行动放进同一个生成序列里,让它们互为输入。今天绝大多数工具型 Agent 的主循环,无论外面套了多少层规划和记忆,最内核仍是这个闭环。
但别把它读成"所有 Agent 都是 ReAct":预定义步骤的 Workflow 没有这个循环(下一步走哪由代码决定,不由模型决定);Plan-and-Execute 把"想"和"做"拆成了两个阶段,执行期不再每步重新推理;多 Agent 编排层调度的是 Agent 而不是工具。ReAct 是**"模型自己决定下一步动作"这一类**系统的内核,不是 Agent 这个词的全部。